package yui.comn.mybatisx.extension.mgr.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;

import yui.comn.mybatisx.core.conditions.Wrapper;
import yui.comn.mybatisx.core.mapper.BaseDao;
import yui.comn.mybatisx.extension.mgr.IMgr;
import yui.comn.mybatisx.extension.node.TreeNode;
import yui.comn.mybatisx.extension.utils.TreeNodeUtils;

/**
 * <p>
 * 基础服务类
 * </p>
 * @author yuyi (1060771195@qq.com)
 */
@SuppressWarnings("unchecked")
public abstract class MgrImpl<M extends BaseDao<T, D>, T, D> implements IMgr<T, D> {
    protected Log log = LogFactory.getLog(getClass());
    
    @Autowired
    public M baseDao;
    
    @Override
    public M getBaseDao() {
        return baseDao;
    }
    
    @Override
    public Class<T> getEntityClass() {
        return entityClass;
    }
    
    protected Class<T> entityClass = currentModelClass();
    
    protected Class<T> mapperClass = currentMapperClass();
    
    protected Class<T> currentMapperClass() {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 0);
    }

    protected Class<T> currentModelClass() {
        return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 1);
    }
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Collection<T> addBatch(Collection<T> entityList, int batchSize) {
        int listSize = entityList.size();
        if (listSize <= batchSize) {
            baseDao.insertBatch(entityList);
            return entityList;
        }
        int i = 0;
        Collection<T> partEntityList = new ArrayList<>();
        for (T anEntityList : entityList) {
            i++;
            partEntityList.add(anEntityList);
            if (i % batchSize == 0 || i == listSize) {
                baseDao.insertBatch(partEntityList);
                partEntityList.clear();
            }
        }
        return entityList;
    }
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Collection<T> addOrUpdateBatch(Collection<T> entityList, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
        
        Collection<T> addList = new ArrayList<>();
        Collection<T> updList = new ArrayList<>();
        for (T entity : entityList) {
            Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
            if (StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) {
                addList.add(entity);
            } else {
                updList.add(entity);
            }
        }
        if (!addList.isEmpty()) {
            addBatch(addList, batchSize);
        }
        if (!updList.isEmpty()) {
            updateBatchById(updList, batchSize);
        }
        return entityList;
    }
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Collection<T> updateBatchById(Collection<T> entityList, int batchSize) {
        int listSize = entityList.size();
        if (listSize <= batchSize) {
            baseDao.updateBatchById(entityList);
            return entityList;
        }
        int i = 0;
        Collection<T> partEntityList = new ArrayList<>();
        for (T anEntityList : entityList) {
            i++;
            partEntityList.add(anEntityList);
            if (i % batchSize == 0 || i == listSize) {
                baseDao.updateBatchById(partEntityList);
                partEntityList.clear();
            }
        }
        return entityList;
    }
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Collection<T> updateAllBatchById(Collection<T> entityList, int batchSize) {
        int listSize = entityList.size();
        if (listSize <= batchSize) {
            baseDao.updateAllBatchById(entityList);
            return entityList;
        }
        int i = 0;
        Collection<T> partEntityList = new ArrayList<>();
        for (T anEntityList : entityList) {
            i++;
            partEntityList.add(anEntityList);
            if (i % batchSize == 0 || i == listSize) {
                baseDao.updateAllBatchById(partEntityList);
                partEntityList.clear();
            }
        }
        return entityList;
    }
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Collection<D> updateBatchByDtoId(Collection<D> entityList, int batchSize) {
        int listSize = entityList.size();
        if (listSize <= batchSize) {
            baseDao.updateBatchByDtoId(entityList);
            return entityList;
        }
        int i = 0;
        Collection<D> partEntityList = new ArrayList<>();
        for (D anEntityList : entityList) {
            i++;
            partEntityList.add(anEntityList);
            if (i % batchSize == 0 || i == listSize) {
                baseDao.updateBatchByDtoId(partEntityList);
                partEntityList.clear();
            }
        }
        return entityList;
    }
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Collection<D> updateAllBatchByDtoId(Collection<D> entityList, int batchSize) {
        int listSize = entityList.size();
        if (listSize <= batchSize) {
            baseDao.updateAllBatchByDtoId(entityList);
            return entityList;
        }
        int i = 0;
        Collection<D> partEntityList = new ArrayList<>();
        for (D anEntityList : entityList) {
            i++;
            partEntityList.add(anEntityList);
            if (i % batchSize == 0 || i == listSize) {
                baseDao.updateAllBatchByDtoId(partEntityList);
                partEntityList.clear();
            }
        }
        return entityList;
    }

    @Override
	public int defunct(Wrapper<T> wrapper) {
		return baseDao.defunct(wrapper);
	}

	@Override
	public int delete(Wrapper<T> wrapper) {
		return baseDao.delete(wrapper);
	}

	@Transactional(rollbackFor = Exception.class)
    @Override
    public int deleteByIds(Collection<? extends Serializable> idList, int batchSize) {
        int listSize = idList.size();
        if (listSize <= batchSize) {
            return baseDao.deleteBatchIds(idList);
        }
        int i = 0;
        int total = 0;
        Collection<Serializable> partIdList = new ArrayList<>();
        for (Serializable id : idList) {
            i++;
            partIdList.add(id);
            if (i % batchSize == 0 || i == listSize) {
                total += baseDao.deleteBatchIds(partIdList);
                partIdList.clear();
            }
        }
        return total;
    }

    @Override
    public T add(T entity) {
        baseDao.insert(entity);
        return entity;
    }
    
    @Override
    public int update(T entity, Wrapper<T> wrapper) {
        return baseDao.update(entity, wrapper);
    }

    @Override
    public int update(T entity) {
        return baseDao.updateById(entity);
    }
    
    @Override
    public int updateAll(T entity) {
        return baseDao.updateAllById(entity);
    }
    
    @Override
    public int deleteById(Serializable id) {
        return baseDao.deleteById(id);
    }
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteTreeById(Serializable id, String pidColumn) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(currentModelClass());
        
        Map<String, Object> columnMap = new HashMap<>();
        columnMap.put(pidColumn, id);
        List<Map<String, Object>> listMaps = this.listMapsByMap(columnMap);
        if (CollectionUtils.isNotEmpty(listMaps)) {
            for (Map<String, Object> map : listMaps) {
                deleteTreeById((Serializable) map.get(tableInfo.getKeyColumn()), pidColumn);
            }
        }
        
        this.deleteById(id);
    }
    
    @Override
    public <TN extends TreeNode<TN>> List<TN> listTreeNode(Wrapper<T> wrapper, Class<TN> treeNodeClass, Long pid) {
        List<T> list = this.listVo(wrapper);
        return TreeNodeUtils.voListHandle(list, treeNodeClass, pid);
    }

    @Override
    public D getById(Serializable id) {
        return baseDao.getById(id);
    }

    @Override
    public D get(Wrapper<T> wrapper, boolean throwEx) {
        if (throwEx) {
            baseDao.get(wrapper);
        }
        return SqlHelper.getObject(log, baseDao.list(wrapper));
    }
    
    @Override
    public D getByMap(Map<String, Object> columnMap) {
        return baseDao.getByMap(columnMap);
    }

    @Override
    public D get(String colomns, Object... values) {
        return baseDao.get(colomns, values);
    }

    @Override
    public Integer count(Wrapper<T> wrapper) {
        return baseDao.count(wrapper);
    }

    @Override
    public List<D> list(Wrapper<T> wrapper) {
        return baseDao.list(wrapper);
    }

    @Override
    public List<D> listByIds(Collection<? extends Serializable> idList) {
        return baseDao.listBatchIds(idList);
    }

    @Override
    public List<D> listByMap(Map<String, Object> columnMap) {
        return baseDao.listByMap(columnMap);
    }
    
    @Override
    public List<Map<String, Object>> listMaps(Wrapper<T> wrapper) {
        return baseDao.listMaps(wrapper);
    }
    
    @Override
    public List<Map<String, Object>> listMapsByMap(Map<String, Object> columnMap) {
        return baseDao.listMapsByMap(columnMap);
    }

    @Override
    public List<Object> listObjs(Wrapper<T> wrapper) {
        return baseDao.listObjs(wrapper);
    }

    @Override
    public <E extends IPage<D>> E page(E page, Wrapper<T> wrapper) {
        return baseDao.page(page, wrapper);
    }

    @Override
    public <E extends IPage<Map<String, Object>>> E pageMaps(E page, Wrapper<T> wrapper) {
        return baseDao.pageMaps(page, wrapper);
    }

    /*****************************************mybaits plus 原始批量操作*****************************************/
    /**
     * 获取mapperStatementId
     *
     * @param sqlMethod 方法名
     * @return 命名id
     * @since 3.4.0
     */
    protected String getSqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.getSqlStatement(mapperClass, sqlMethod);
    }
    
    /**
     * 执行批量操作
     *
     * @param list      数据集合
     * @param batchSize 批量大小
     * @param consumer  执行方法
     * @param <E>       泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(this.entityClass, this.log, list, batchSize, consumer);
    }
    
    @Override
    public boolean addBatchOrig(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
    }

    @Override
    public boolean addOrUpdateBatchOrig(Collection<T> entityList, int batchSize) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
        return SqlHelper.saveOrUpdateBatch(this.entityClass, this.mapperClass, this.log, entityList, batchSize, (sqlSession, entity) -> {
            Object idVal = ReflectionKit.getFieldValue(entity, keyProperty);
            return StringUtils.checkValNull(idVal)
                || CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
        }, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
        });
    }

    @Override
    public boolean updateBatchByIdOrig(Collection<T> entityList, int batchSize) {
        String sqlStatement = getSqlStatement(SqlMethod.UPDATE_BY_ID);
        return executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(sqlStatement, param);
        });
    }
    
}
